package crmdna.inventory;

import static crmdna.common.AssertUtils.ensure;
import static crmdna.common.AssertUtils.ensureNotNull;
import static crmdna.common.OfyService.ofy;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import com.googlecode.objectify.VoidWork;

import crmdna.client.Client;
import crmdna.group.Group;
import crmdna.inventory.InventoryItemChangeQueryCondition.CheckInFilter;
import crmdna.user.User;

public class InventoryItemChange {

	static void recordChange(final String client, long groupId,
			final InventoryItemChangeProp prop) {
		Client.ensureValid(client);

		Group.safeGet(client, groupId);

		ensureNotNull(prop);

		if (prop.ms == 0)
			prop.ms = new Date().getTime();
		int bucketNo = getBucketNo(prop.ms);

		final String id = getEntityId(groupId, bucketNo);

		ofy(client).transact(new VoidWork() {

			@Override
			public void vrun() {
				InventoryItemChangeEntity entity = ofy(client).load()
						.type(InventoryItemChangeEntity.class).id(id).get();

				if (entity == null) {
					entity = new InventoryItemChangeEntity();
					entity.groupIdDelimiter10DayBucket = id;
				}

				if (prop.changeDescription != null) {
					if (prop.changeDescription.length() > 255)
						prop.changeDescription = prop.changeDescription
								.substring(0, 255);
				}

				entity.changes.add(prop);

				ofy(client).save().entity(entity).now();

				// if the entity size exceeds 1Mb gae will not allow to save.
				// an exception
				// will be thrown and dev team will be notified
			}
		});
	}

	public static List<InventoryItemChangeProp> query(String client,
			InventoryItemChangeQueryCondition qc, String login) {
		
		Client.ensureValid(client);
		User.ensureValidUser(client, login);
		ensureNotNull(qc);

		Group.safeGet(client, qc.groupId);
		ensure(qc.startMS > 0);
		ensure(qc.endMS > 0);
		ensure(qc.endMS >= qc.startMS,
				"endMS should be greater or equal to startMS");

		TreeSet<InventoryItemChangeProp> treeSet = InventoryItemChange
				.getChanges(client, qc.groupId, qc.startMS, qc.endMS);

		ensureNotNull(treeSet,
				"treeSet after InventoryItemChange.getChanges is null");

		Set<Long> inventoryItemIds = new HashSet<>();

		for (InventoryItemChangeProp prop : treeSet) {
			inventoryItemIds.add(prop.inventoryItemId);
		}

		Map<Long, InventoryItemEntity> map = InventoryItem.get(client,
				inventoryItemIds);

		List<InventoryItemChangeProp> changes = new ArrayList<>();
		for (InventoryItemChangeProp prop : changes) {
			if (! map.containsKey(prop.inventoryItemId)) {
				//inventory item has been deleted
				continue;
			}
			
			if (isConditionSatisfied(prop, qc, map))
				changes.add(prop);
		}

		return changes;
	}

	static boolean isConditionSatisfied(InventoryItemChangeProp prop,
			InventoryItemChangeQueryCondition qc,
			Map<Long, InventoryItemEntity> map) {
		
		ensureNotNull(map);
		ensure(! map.isEmpty());

		if (qc.checkInFilter != null) {
			if (qc.checkInFilter == CheckInFilter.ONLY_CHECKIN)
				if (prop.changeInDefaultUnit < 0)
					return false;

			if (qc.checkInFilter == CheckInFilter.ONLY_CHECKOUT)
				if (prop.changeInDefaultUnit > 0)
					return false;
		}

		if ((qc.departmentIds != null) && !qc.departmentIds.isEmpty()) {
			if (prop.departmentId == null)
				return false;

			if (!qc.departmentIds.contains(prop.departmentId)) {
				return false;
			}
		}

		if ((qc.inventoryItemIds != null) && !qc.inventoryItemIds.isEmpty()) {
			if (!qc.inventoryItemIds.contains(prop.inventoryItemId))
				return false;
		}

		if ((qc.inventoryItemTypeIds != null)
				&& !qc.inventoryItemTypeIds.isEmpty()) {
			if (!map.containsKey(prop.inventoryItemId))
				return false; // should never happen

			if (!qc.inventoryItemTypeIds
					.contains(map.get(prop.inventoryItemId).inventoryItemTypeId))
				return false;
		}

		return true;
	}

	static TreeSet<InventoryItemChangeProp> getChanges(String client,
			long groupId, long startMS, long endMS) {

		int startBucketNo = getBucketNo(startMS);
		int endBucketNo = getBucketNo(endMS);

		List<String> ids = new ArrayList<>();

		for (int bucketNo = endBucketNo; bucketNo >= startBucketNo; bucketNo--) {
			ids.add(getEntityId(groupId, bucketNo));
		}

		Map<String, InventoryItemChangeEntity> map = ofy(client).load()
				.type(InventoryItemChangeEntity.class).ids(ids);

		TreeSet<InventoryItemChangeProp> treeSet = new TreeSet<>();
		for (int i = 0; i < ids.size(); i++) {
			String id = ids.get(i);

			if (!map.containsKey(id))
				continue;

			if (i < ids.size() - 1) {
				// not the last bucket
				treeSet.addAll(map.get(id).changes);
				continue;
			}

			// last element in ids list
			for (InventoryItemChangeProp prop : map.get(id).changes) {
				if (prop.ms < startMS)
					break;

				treeSet.add(prop);
			}
		}

		return treeSet;
	}

	static int getBucketNo(long ms) {
		ensure(ms > 0, "Time in ms [" + ms + "] is negative");

		int bucketNo = (int) (ms / 1000 / 86400 / 10);

		return bucketNo;
	}

	static String getEntityId(long groupId, long bucketNo) {
		return groupId + "||" + bucketNo;
	}
}
